package jsr292.java.lang.invoke;

import java.util.ArrayList;

import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerVarAllocator;

public class MutableCallSite extends CallSite {
    public MutableCallSite(MethodType type) {
        super(type);
    }
    
    public MutableCallSite(MethodHandle target) {
        super(target);
    }

    @Override
    public final MethodHandle getTarget() {
        return super.getTarget();
    }
    @Override
    public void setTarget(MethodHandle target) {
        super.setTarget(target);
    }
    
    public static void syncAll(@SuppressWarnings("unused") MutableCallSite[] unused) {
        // do nothing it should just work
    }
    
    @Override
    public final MethodHandle dynamicInvoker() {
        return new DynamicInvokerMH(type(), this);
    }
    
    static class DynamicInvokerMH extends MethodHandle {
        private final MutableCallSite callSite;
        
        DynamicInvokerMH(MethodType type, MutableCallSite callSite) {
            super(type);
            this.callSite = callSite;
        }
        @Override
        public Object $invoke$() throws Throwable {
            return callSite.target.$invoke$();
        }
        @Override
        public Object $invoke$(Object o1) throws Throwable {
            return callSite.target.$invoke$(o1);
        }
        @Override
        public Object $invoke$(Object o1, Object o2) throws Throwable {
            return callSite.target.$invoke$(o1, o2);
        }
        @Override
        public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
            return callSite.target.$invoke$(o1, o2, o3);
        }
        @Override
        public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
            return callSite.target.$invoke$(o1, o2, o3, o4);
        }
        @Override
        public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
            return callSite.target.$invoke$(o1, o2, o3, o4, o5);
        }
        @Override
        public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
            return callSite.target.$invoke$(o1, o2, o3, o4, o5, o6);
        }
        @Override
        public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
            return callSite.target.$invoke$(o1, o2, o3, o4, o5, o6, o7);
        }
        @Override
        public Object $invoke$(Object... rest) throws Throwable {
            return callSite.target.$invoke$(rest);
        }
        
        @Override
        public boolean isWeavable(Class<?> classBeingRedefined) {
            return callSite.target.isWeavable(classBeingRedefined);
        }

        @Override
        public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
            // if it's a method call, not an invokedynamic
            CallSite optimizingCallSite = context.getCallSite();
            if (optimizingCallSite == null) {
                MHExactInvoker exactInvoker = (MHExactInvoker)MethodHandles.exactInvoker(type());
                exactInvoker.mhCache = callSite.target;  // fastpath for the current target
                MethodHandles.foldArguments(exactInvoker, GET_TARGET.bindTo(callSite)).
                    weave(context, allocator);
                return;
            }
            
            // add a dependency between the dynamic invoker and the callsite
            synchronized(callSite.callerSet) {
                if (callSite.dependencies == null) {
                    callSite.dependencies = new ArrayList<CallSite>();
                }
                callSite.dependencies.add(optimizingCallSite);
            }
            callSite.target.weave(context, allocator);
        }
        
        private static final MethodHandle GET_TARGET;
        static {
            try {
                GET_TARGET = MethodHandles.publicLookup().findGetter(CallSite.class, "target", MethodHandle.class);
            } catch (NoSuchFieldException e) {
                throw (AssertionError)new AssertionError().initCause(e);
            } catch (IllegalAccessException e) {
                throw (AssertionError)new AssertionError().initCause(e);
            }
        }
    }
}
